package app

import (
	"bufio"
	"encoding/json"
	"fmt"
	"github.com/Masterminds/semver/v3"
	"github.com/go-resty/resty/v2"
	"log/slog"
	"npm/utils"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"sync"
	"time"
)

type PackageJSONDownSample struct {
	dir             string
	dependenciesUrl []string
}

func PackageJSONDownRun(dir string) error {
	down := PackageJSONDownSample{dir: dir}
	return down.Start()
}

func (ds *PackageJSONDownSample) Start() error {
	ds.ParsePackageJSONFile()
	ds.WriteUrls()
	ds.Download()
	return nil
}

func (ds *PackageJSONDownSample) Download() {
	downReader := bufio.NewReader(os.Stdin)
	fmt.Print("依赖解析完成。是否开始下载？输入：(y) 立即开始下载 (n) 退出程序。\n: ")
	confirm, _ := downReader.ReadString('\n')
	confirm = strings.TrimSpace(confirm)
	if confirm != "y" {
		return
	}

	//start downloads
	var wg sync.WaitGroup
	semaphore := make(chan struct{}, utils.Conf.MaxConcurrentRequests)
	downloadDir := fmt.Sprintf("%s/registry", utils.GetCurrentDir())
	for _, url := range ds.dependenciesUrl {
		semaphore <- struct{}{}
		wg.Add(1)
		if _, err := os.Stat(downloadDir); os.IsNotExist(err) {
			_ = os.MkdirAll(downloadDir, 0755)
		}

		go utils.Down(downloadDir, url, &wg, semaphore)
	}

	wg.Wait()
}

func (ds *PackageJSONDownSample) WriteUrls() {
	//write urls file
	filename := utils.SanitizeFileName(ds.dir)
	logFilePath := fmt.Sprintf("%s/%s_%s.log", utils.GetCurrentDir(), filename, time.Now().Format("2006-01-02_150405"))
	file, logErr := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	defer file.Close() // 确保在函数结束时关闭文件

	//write logs
	for _, url := range ds.dependenciesUrl {
		if logErr == nil {
			_, _ = file.WriteString(url + "\n") // 添加换行符以分隔内容
		}
	}
}

func (ds *PackageJSONDownSample) ParsePackageJSONFile() {
	if ds.dir == "" {
		return
	}

	if !utils.CheckFileExistence(ds.dir) {
		slog.Error("文件没有找到", "path", ds.dir)
		return
	}

	dependencies, findErr := ds.FindPackageJsonFiles(ds.dir)
	if findErr != nil {
		slog.Error("package.json 查找失败", "error", findErr)
		os.Exit(1)
	}

	if len(dependencies) == 0 {
		slog.Error("无法从 package.json 获取依赖")
		os.Exit(1)
	}

	var wg sync.WaitGroup
	results := make(chan []string)
	semaphore := make(chan struct{}, utils.Conf.MaxConcurrentRequests)

	go func() {
		for res := range results {
			ds.dependenciesUrl = append(ds.dependenciesUrl, res...)
		}
	}()

	for k, v := range dependencies {
		fmt.Printf("k： %#v, v: %#v\n", k, v)
		semaphore <- struct{}{}
		wg.Add(1)
		go ds.resolveVersion(k, v, &wg, semaphore, results)
	}

	wg.Wait()
	close(results)

	ds.dependenciesUrl = ds.removeDuplicateElement(ds.dependenciesUrl)
}

func (ds *PackageJSONDownSample) resolveVersion(pkgName string, constraint string, wg *sync.WaitGroup, ch chan struct{}, results chan<- []string) {
	defer func() {
		<-ch
		wg.Done()
	}()

	err := ds.getVersions(pkgName, constraint)
	if err != nil {
		return
	}
}

func (ds *PackageJSONDownSample) getVersions(packageName string, constraint string) error {
	urlStr := fmt.Sprintf("%s%s", utils.Conf.Registry, packageName)
	client := resty.New()
	client.SetHeaders(map[string]string{"Content-Type": "application/json", "User-Agent": utils.Conf.UserAgent})
	resp, err := client.R().EnableTrace().Get(urlStr)
	if err != nil {
		slog.Error("依赖信息请求失败:", "Error", err)
		return err
	}

	body := resp.Body()
	versionInfo := utils.DependenciesInfo{}
	err = json.Unmarshal(body, &versionInfo)
	if err != nil {
		slog.Error("依赖版本信息解析失败:", "Error", err)
		return err
	}

	if versionInfo.ErrorMessage != "" {
		slog.Error("依赖信息读取失败:", "Error", versionInfo.ErrorMessage)
		return err
	}

	slog.Info(fmt.Sprintf("解析: %s 依赖关系...", packageName))

	var versions semver.Collection
	for version := range versionInfo.Versions {
		v, err := semver.NewVersion(version)
		if err != nil {
			continue
		}
		versions = append(versions, v)
	}

	sort.Sort(versions)

	checker, err := semver.NewConstraint(constraint)
	if err != nil {
		slog.Error("unable to create a new constraint for", "constraint", fmt.Sprintf("%s@%s", packageName, constraint), "error", err)
		return err
	}

	currentVersionInfo := utils.DependenciesVersion{}
	for i := len(versions) - 1; i >= 0; i-- {
		if checker.Check(versions[i]) {
			currentVersion := versions[i].String()
			currentVersionInfo = versionInfo.Versions[currentVersion]
			break
		}
	}

	fmt.Printf("currentVersionInfo.Version: %#v\n", currentVersionInfo.Version)

	return nil
}

func (ds *PackageJSONDownSample) FetchVersions(packageName string, constraints []string, wg *sync.WaitGroup, ch chan struct{}, results chan<- []string) {
	defer func() {
		<-ch
		wg.Done()
	}()

	urlStr := fmt.Sprintf("%s%s", utils.Conf.Registry, packageName)
	client := resty.New()
	client.SetHeaders(map[string]string{"Content-Type": "application/json", "User-Agent": utils.Conf.UserAgent})
	resp, err := client.R().EnableTrace().Get(urlStr)
	if err != nil {
		slog.Error("依赖信息请求失败:", "Error", err)
		return
	}

	body := resp.Body()
	versionInfo := utils.DependenciesInfo{}
	err = json.Unmarshal(body, &versionInfo)
	if err != nil {
		slog.Error("依赖版本信息解析失败:", "Error", err)
		return
	}

	if versionInfo.ErrorMessage != "" {
		slog.Error("依赖信息读取失败:", "Error", versionInfo.ErrorMessage)
		return
	}

	slog.Info(fmt.Sprintf("解析: %s 依赖关系...", packageName))

	versionKeys := utils.GetKeys1(versionInfo.Time)
	var versions []string
	for _, ver := range versionKeys {
		if utils.IsAllEnglishLetters(ver) {
			continue
		}

		versions = append(versions, ver)
	}

	semverVersions := ds.versionSort(versions)
	var dependencies []string
	var vers []string
	for _, constraint := range constraints {
		for _, ver := range semverVersions {
			if allow, _ := ds.satisfiesVersion(ver.String(), constraint); allow {
				//downVer := versionInfo.Versions[ver]
				//fileUrl := downVer.Dist.Tarball
				//dependencies = append(dependencies, fileUrl)
				vers = append(vers, ver.String())
				break
			}
		}
	}

	if len(vers) == 0 {
		slog.Error("版本为空", "packageName", packageName, "constraints", strings.Join(constraints, ","))
		return
	}

	semverVers := ds.versionSort(vers)
	latestVersion := semverVers[0].String()
	if downVer, exists := versionInfo.Versions[latestVersion]; exists {
		fileUrl := downVer.Dist.Tarball
		dependencies = append(dependencies, fileUrl)
	}

	results <- dependencies
}

func (ds *PackageJSONDownSample) versionSort(versions []string) []*semver.Version {
	semverVersions := make([]*semver.Version, len(versions))
	for i, raw := range versions {
		v, err := semver.NewVersion(raw)
		if err != nil {
			slog.Error("Error parsing version", "Error", err)
			continue
		}
		semverVersions[i] = v
	}

	sort.Slice(semverVersions, func(i, j int) bool {
		return semverVersions[i].GreaterThan(semverVersions[j])
	})

	return semverVersions
}

func (ds *PackageJSONDownSample) parseVersions(versions []string) []string {
	processedVersions := make([]string, 0, len(versions))
	for _, v := range versions {
		// 处理带有 || 的版本号
		parts := strings.Split(v, "||")
		for _, part := range parts {
			part = strings.TrimSpace(part)

			// 去掉以 ^、~、>=、<=、>、< 开头的字符
			part = regexp.MustCompile(`^[\^~>=<=><]`).ReplaceAllString(part, "")
			part = strings.ReplaceAll(part, "=", "")
			part = strings.TrimSpace(part)

			// 检查是否符合 x.x.x 格式或包含 -dev 后缀
			matched, _ := regexp.MatchString(`\d+\.\d+\.\d+(-\w+)?`, part)
			if matched {
				processedVersions = append(processedVersions, part)
			}
		}
	}

	return processedVersions
}

func (ds *PackageJSONDownSample) FetchVersionsByNpm(packageName string, constraints []string, wg *sync.WaitGroup, ch chan struct{}, results chan<- []string) {
	defer func() {
		<-ch
		wg.Done()
	}()

	var dependencies []string
	tmpDependencies := map[string]string{}
	env := "bash"
	if runtime.GOOS == "windows" {
		env = "cmd"
	}

	for _, constraint := range constraints {
		pname := fmt.Sprintf("%s@'%s'", packageName, constraint)
		cmdline := fmt.Sprintf("npm view %s --json", pname)
		cmd := exec.Command(env, "-c", cmdline)
		cmd.Env = os.Environ()
		output, err := cmd.CombinedOutput()
		if err != nil {
			slog.Error("执行命令失败", "packageName", pname, "Error", err, "Output", output)
			continue
		}

		isObject, isArray, isError := ds.isJSONObjectOrArray(string(output))
		if isError != nil {
			slog.Error("JSON 格式有误", "packageName", packageName, "Error", isError.Error())
			continue
		}

		slog.Info(fmt.Sprintf("解析: %s 依赖关系...", packageName))

		if isObject {
			var packageInfo utils.NPMViewInfo
			err = json.Unmarshal(output, &packageInfo)
			if err != nil {
				slog.Error("解析 JSON 输出失败", "packageName", pname, "Error", err, "Output", output)
				continue
			}

			tmpDependencies[packageInfo.Dist.Tarball] = ""
		}

		if isArray {
			var packageInfos []utils.NPMViewInfo
			err = json.Unmarshal(output, &packageInfos)
			if err != nil {
				slog.Error("解析 JSON 输出失败", "packageName", pname, "Error", err, "Output", output)
				continue
			}

			for _, packageInfo := range packageInfos {
				tmpDependencies[packageInfo.Dist.Tarball] = ""
			}
		}
	}

	for dependency, _ := range tmpDependencies {
		dependencies = append(dependencies, dependency)
	}

	tmpDependencies = nil

	results <- dependencies
}

func (ds *PackageJSONDownSample) isJSONObjectOrArray(s string) (isObject bool, isArray bool, err error) {
	var js map[string]interface{}
	var ja []interface{}

	// 尝试解析为 JSON 对象
	err = json.Unmarshal([]byte(s), &js)
	if err == nil {
		isObject = true
		return
	}

	// 尝试解析为 JSON 数组
	err = json.Unmarshal([]byte(s), &ja)
	if err == nil {
		isArray = true
		return
	}

	// 如果都不是，则返回错误
	return false, false, fmt.Errorf("neither JSON object nor array: %v", err)
}

func (ds *PackageJSONDownSample) FindPackageJsonFiles(path string) (map[string]string, error) {
	dependencies := map[string]string{}
	jsonBody, jsonErr := os.ReadFile(path)
	if jsonErr != nil {
		slog.Error("文件读取失败", "path", path, "err", jsonErr)
		return nil, jsonErr
	}

	packageInfo := utils.PackageJson{}
	jErr := json.Unmarshal(jsonBody, &packageInfo)
	if jErr != nil {
		slog.Error("package.json 文件解析失败", "path", path, "err", jsonErr)
		return nil, jErr
	}

	for name, v := range packageInfo.Dependencies {
		dependencies[name] = v
	}

	for name, v := range packageInfo.DevDependencies {
		dependencies[name] = v
	}

	for name, v := range packageInfo.PeerDependencies {
		dependencies[name] = v
	}

	for name, v := range packageInfo.BundledDependencies {
		dependencies[name] = v
	}

	for name, v := range packageInfo.OptionalDependencies {
		dependencies[name] = v
	}

	return dependencies, nil
}

func (ds *PackageJSONDownSample) removeDuplicateElement(values []string) []string {
	result := make([]string, 0, len(values))
	temp := map[string]struct{}{}
	for _, item := range values {
		if _, ok := temp[item]; !ok {
			temp[item] = struct{}{}
			result = append(result, item)
		}
	}
	return result
}

func (ds *PackageJSONDownSample) satisfiesVersion(version, constraint string) (bool, error) {
	c, err := semver.NewConstraint(constraint)
	if err != nil {
		return false, err
	}

	v, err := semver.NewVersion(version)
	if err != nil {
		return false, err
	}
	return c.Check(v), nil
}
